//
//  YVSViewController.m
//  YoutubeVideoSearchDemo
//
//  Created by eggswift on 2021/9/26.
//

#import "YVSViewController.h"
#import "JGProgressHUD.h"
#import "YVSTableViewCell.h"

#define YVS_CHECK_VALID_OBJECT(__aClass, __aObject) ([(id)__aObject isKindOfClass:[__aClass class]])
#define YVS_VALID_OBJECT(__aClass, __aObject) ((__aClass *)(YVS_CHECK_VALID_OBJECT(__aClass, __aObject) ? __aObject : nil))

#define YVS_CHECK_VALID_STRING(__aString) (__aString && [(id)__aString isKindOfClass:[NSString class]] && [(NSString *)__aString length])
#define YVS_VALID_STRING(__aString) (YVS_CHECK_VALID_STRING(__aString) ? (NSString *)__aString : @"")
#define YVS_STRING(__aString, __defaultString) (YVS_CHECK_VALID_STRING(__aString) ? (NSString *)__aString : (NSString *)__defaultString)

#define YVS_CHECK_VALID_NUMBER(__aNumber) (__aNumber && [(id)__aNumber isKindOfClass:[NSNumber class]])
#define YVS_VALID_NUMBER(__aNumber) (YVS_CHECK_VALID_NUMBER(__aNumber) ? (NSNumber *)__aNumber : @(0))
#define YVS_NUMBER(__aNumber, __defaultNumber) (YVS_CHECK_VALID_NUMBER(__aNumber) ? (NSNumber *)__aNumber : (NSNumber *)__defaultNumber)

#define YVS_CHECK_VALID_ARRAY(__aArray) (__aArray && [(id)__aArray isKindOfClass:[NSArray class]] && [(NSArray *)__aArray count])
#define YVS_VALID_ARRAY(__aArray) (YVS_CHECK_VALID_ARRAY(__aArray) ? (NSArray *)__aArray : @[])
#define YVS_ARRAY(__aArray, __defaultArray) (YVS_CHECK_VALID_ARRAY(__aArray) ? (NSArray *)__aArray : (NSArray *)__defaultArray)

#define YVS_CHECK_VALID_DICTIONARY(__aDictionary) (__aDictionary && [(id)__aDictionary isKindOfClass:[NSDictionary class]] && [(NSDictionary *)__aDictionary count])
#define YVS_VALID_DICTIONARY(__aDictionary) (YVS_CHECK_VALID_DICTIONARY(__aDictionary) ? (NSDictionary *)__aDictionary : @{})
#define YVS_DICTIONARY(__aDictionary, __defaultDictionary) (YVS_CHECK_VALID_DICTIONARY(__aDictionary) ? (NSDictionary *)__aDictionary : (NSDictionary *)__defaultDictionary)


@interface YVSViewController () <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) JGProgressHUD *hud;
@property (nonatomic, strong) NSString *searchKey;
@property (nonatomic, strong) NSArray *resultArray;
@end

@implementation YVSViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupNavigationBar];
    [self setupView];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    
}

- (void)setupNavigationBar {
    self.title = self.searchKey.length ? [NSString stringWithFormat:@"Searching result for '%@'", self.searchKey] : @"Demo";
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Search" style:UIBarButtonItemStylePlain target:self action:@selector(searchAction:)];
}

- (void)setupView {
    [self.view addSubview:self.tableView];
}

- (void)reloadDataWithResultArray:(NSArray *)resultArray {
    self.resultArray = [resultArray copy];
    self.title = self.searchKey.length ? [NSString stringWithFormat:@"Searching result for '%@'", self.searchKey] : @"Demo";
    [self.tableView reloadData];
}

#pragma mark - HUD
- (void)showLoadingHUD {
    self.hud.textLabel.text = @"Searching...";
    [self.hud showInView:self.view animated:YES];
}

- (void)hideLoadingHUD {
    [self.hud dismissAnimated:YES];
}


#pragma mark - Search
- (void)searchWithQuerly:(NSString *)query {
    self.searchKey = query;
    [self showLoadingHUD];
    __weak typeof(self) weakSelf = self;
    [self requestWithQuerly:query completionHandler:^(NSArray *resultArray, NSError *error) {
        [self hideLoadingHUD];
        __strong typeof(self) strongSelf = weakSelf;
        if (error) {
            [strongSelf showAlertWithRequestError:error];
        }
        else {
            [strongSelf reloadDataWithResultArray:resultArray ?: @[]];
        }
    }];
}

- (void)showAlertWithRequestError:(NSError *)error {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Search Error" message:error.domain preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"Try again" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [self searchWithQuerly:self.searchKey];
    }]];
    [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        // do nothing...
    }]];
    [self presentViewController:alertController animated:YES completion:^{}];
}

/// https://www.youtube.com/results?search_query=query
- (void)requestWithQuerly:(NSString *)query completionHandler:(void (^) (NSArray *resultArray, NSError *error))completionHandler {
    if (!query.length) {
        completionHandler(@[], nil);
        return;
    }
    
    NSString *searchStrWithoutSpace = [query stringByReplacingOccurrencesOfString:@" " withString:@"+"];
    NSString *searchStrUrl = [searchStrWithoutSpace stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://www.youtube.com/results?search_query=%@", searchStrUrl]];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            completionHandler(nil, error);
            return;
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            NSError *customError = [NSError errorWithDomain:@"YoutubeSearchEngineParserErrorDomain" code:1 userInfo:nil];
            NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSRange dataBeginRange = [responseString rangeOfString:@"var ytInitialData ="];
            if (dataBeginRange.length == 0) {
                completionHandler(nil, customError);
                return;
            }
                    
            NSRange dataEndRange = [responseString rangeOfString:@"</script>" options:NSCaseInsensitiveSearch range:NSMakeRange(dataBeginRange.location, responseString.length - dataBeginRange.location)];
            if (dataEndRange.length == 0) {
                completionHandler(nil, customError);
                return;
            }
            
            NSInteger beginIdx = dataBeginRange.location + dataBeginRange.length;
            NSInteger length = dataEndRange.location - beginIdx;
            NSRange dataRange = NSMakeRange(beginIdx, length);
            NSString *dataStr = [responseString substringWithRange:dataRange];
            
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x0a" withString:@"\n"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x21" withString:@"!"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x22" withString:@"\""];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x24" withString:@"$"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x26" withString:@"&"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x27" withString:@"'"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x28" withString:@"("];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x29" withString:@")"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x2a" withString:@"*"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x2d" withString:@"-"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x3b" withString:@";"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x3c" withString:@"<"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x3e" withString:@">"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x3f" withString:@"?"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x5b" withString:@"["];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x5c" withString:@"\\"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x5d" withString:@"]"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x5e" withString:@"^"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x60" withString:@"`"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x7b" withString:@"{"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x7c" withString:@"|"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x7d" withString:@"}"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x7e" withString:@"~"];
            dataStr = [dataStr stringByReplacingOccurrencesOfString:@"\\x3d" withString:@":"];
            
            if ([dataStr hasPrefix:@" '"]) {
                dataStr = [dataStr substringWithRange:NSMakeRange(@" '".length, dataStr.length - @" '".length)];
            }
            
            if ([dataStr hasSuffix:@"';"]) {
                dataStr = [dataStr substringWithRange:NSMakeRange(0, dataStr.length - @"';".length)];
            }
            
            NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[dataStr dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingFragmentsAllowed error:nil];
            
            NSDictionary *contents = [dict objectForKey:@"contents"];
            if (![contents isKindOfClass:[NSDictionary class]]) {
                completionHandler(nil, [NSError errorWithDomain:@"YoutubeSearchEngineParserErrorDomain" code:1 userInfo:nil]);
                return;
            }
            
            NSDictionary *sectionListRenderer = [contents objectForKey:@"sectionListRenderer"];
            if (![sectionListRenderer isKindOfClass:[NSDictionary class]]) {
                completionHandler(nil, [NSError errorWithDomain:@"YoutubeSearchEngineParserErrorDomain" code:1 userInfo:nil]);
                return;
            }
            
            NSArray *sectionListRendererContents = [sectionListRenderer objectForKey:@"contents"];
            if (![sectionListRendererContents isKindOfClass:[NSArray class]]) {
                completionHandler(nil, [NSError errorWithDomain:@"YoutubeSearchEngineParserErrorDomain" code:1 userInfo:nil]);
                return;
            }
            
            NSDictionary *itemSectionRendererDict = [sectionListRendererContents firstObject];
            if (![itemSectionRendererDict isKindOfClass:[NSDictionary class]]) {
                completionHandler(nil, [NSError errorWithDomain:@"YoutubeSearchEngineParserErrorDomain" code:1 userInfo:nil]);
                return;
            }
            
            NSDictionary *itemSectionRenderer = [itemSectionRendererDict objectForKey:@"itemSectionRenderer"];
            if (![itemSectionRenderer isKindOfClass:[NSDictionary class]]) {
                completionHandler(nil, [NSError errorWithDomain:@"YoutubeSearchEngineParserErrorDomain" code:1 userInfo:nil]);
                return;
            }
            
            NSArray *itemSectionRendererContents = [itemSectionRenderer objectForKey:@"contents"];
            if (![itemSectionRendererContents isKindOfClass:[NSArray class]]) {
                completionHandler(nil, [NSError errorWithDomain:@"YoutubeSearchEngineParserErrorDomain" code:1 userInfo:nil]);
                return;
            }
            
            completionHandler(itemSectionRendererContents, nil);
        });
    }];
    
    [task resume];
}

- (NSString *)hexStringWithData:(NSData *)data {
    const unsigned char* dataBuffer = (const unsigned char*)[data bytes];
    if(!dataBuffer){
        return nil;
    }
    NSUInteger dataLength = [data length];
    NSMutableString* hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
    for(int i = 0; i < dataLength; i++){
        [hexString appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]];
    }
    NSString* result = [NSString stringWithString:hexString];
    return result;
}

- (NSString *)stringFromHexString:(NSString *)hexString {
    char *myBuffer = (char *)malloc((int)[hexString length] / 2 + 1);
    bzero(myBuffer, [hexString length] / 2 + 1);
    for (int i = 0; i < [hexString length] - 1; i += 2) {
        unsigned int anInt;
        NSString * hexCharStr = [hexString substringWithRange:NSMakeRange(i, 2)];
        NSScanner * scanner = [[NSScanner alloc] initWithString:hexCharStr];
        [scanner scanHexInt:&anInt];
        myBuffer[i / 2] = (char)anInt;
    }
    NSString *unicodeString = [NSString stringWithCString:myBuffer encoding:4];
    return unicodeString;
}

#pragma mark - Action
- (void)searchAction:(id)sender {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Input search key words..." message:@"" preferredStyle:UIAlertControllerStyleAlert];
    __weak typeof(alertController) weakAlertController = alertController;
    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.text = self.searchKey;
    }];
    [alertController addAction:[UIAlertAction actionWithTitle:@"Search" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
        __strong typeof(alertController) strongAlertController = weakAlertController;
        if (strongAlertController.textFields.firstObject) {
            NSString *key = strongAlertController.textFields.firstObject.text;
            [self searchWithQuerly:key];
        }
    }]];
    [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        // do nothing...
    }]];
    [self presentViewController:alertController animated:YES completion:^{}];
}

#pragma mark - UITableViewDelegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.resultArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    YVSTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([YVSTableViewCell class]) forIndexPath:indexPath];
    cell.data = self.resultArray[indexPath.row];
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 110.0;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    NSDictionary *item = [self.resultArray objectAtIndex:indexPath.row];
    NSString *videoId = YVS_VALID_STRING(YVS_VALID_DICTIONARY(item[@"compactVideoRenderer"])[@"videoId"]);
    if (!videoId) {
        [self showAlertWithRequestError:[NSError errorWithDomain:@"Cannot find video!" code:1 userInfo:nil]];
        return;
    }
    
    NSString *urlString = [NSString stringWithFormat:@"https://www.youtube.com/watch?v=%@", videoId];
    NSURL *url = [NSURL URLWithString:urlString];
    if (![[UIApplication sharedApplication] canOpenURL:url]) {
        [self showAlertWithRequestError:[NSError errorWithDomain:@"Cannot play video!" code:1 userInfo:nil]];
        return;
    }
    
    if (@available(iOS 10.0, *)) {
        [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
    } else {
        [[UIApplication sharedApplication] openURL:url];
    }
}

#pragma mark - SET/GET
- (UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
        [_tableView registerClass:[YVSTableViewCell class] forCellReuseIdentifier:NSStringFromClass([YVSTableViewCell class])];
        _tableView.alwaysBounceVertical = YES;
        _tableView.backgroundColor = [UIColor clearColor];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        _tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
        _tableView.tableFooterView = [UIView new];
    }
    return _tableView;
}

- (JGProgressHUD *)hud {
    if (!_hud) {
        _hud = [[JGProgressHUD alloc] initWithStyle:JGProgressHUDStyleLight];
    }
    return _hud;
}

@end
